﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using PentaBiz.Interop;

namespace PentaBiz.Collections.DataCache
{
    /// <summary>
    /// Persisted append only sequential data cache,
    /// Keeps only partion of the whole data collection in memory.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class SequentialDataCache<T> : ISequentialDataCache<T>, IDisposable where T : class
    {
        private readonly bool _deleteOnClose;
        private readonly string _path;

        private SequentialDataCacheStructures.Head _head;
        private ObjectPool<Stream> _readerPool;
        private Stream _writerStream;
        private bool disposed;
        private bool closed;
        internal List<long> pageLookup = new List<long>();
        internal List<long> dataLookup = new List<long>();

        /// <summary>
        /// Initializes a new instance of the <see cref="SequentialDataCache{T}" /> class.
        /// </summary>
        /// <param name="path">The path.</param>
        /// <param name="deleteOnClose">if set to <c>true</c> [delete on close].</param>
        /// <exception cref="System.ArgumentNullException">path</exception>
        public SequentialDataCache(string path, bool deleteOnClose = false)
        {
            if (path == null) throw new ArgumentNullException("path");

            _path = path;
            _deleteOnClose = deleteOnClose;

            //single writer - multi reader pattern

            //create reader pool
            _readerPool = new ObjectPool<Stream>(() =>
                                                 File.Open(Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
                );

            //create writer stream
            _writerStream = File.Open(Path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read);

            //if file is empty create default structure
            var fi = new FileInfo(path);
            if (fi.Length == 0)
            {
                _head = new SequentialDataCacheStructures.Head();
                _head.Initialize();
                _head.Persist(_writerStream);
            }
            else
            {
                //load head from the file...
                using (ObjectPoolItem<Stream> rp = _readerPool.GetObject())
                {
                    _head = SequentialDataCacheStructures.Head.LoadFrom(rp.Instance);


                    //fill lookups
                    SequentialDataCacheStructures.Page page;
                    page = _head.OpenFirstPage(rp.Instance);


                    for (int i = 0; i < _head.LastPage.PageIndex; i++)
                    {
                        pageLookup.Add(page.PageOffset);

                        for (int j = 0; j < page.DataCount; j++)
                        {
                            dataLookup.Add(page.DataOffsets[j]);
                        }

                        page = page.ReadNextPage(rp.Instance);
                    }
                }
            }


        }

        /// <summary>
        /// Gets the Item at the specified index.
        /// </summary>
        /// <value>
        /// The Item.
        /// </value>
        /// <param name="index">The index.</param>
        /// <returns>Item at the specified index</returns>
        /// <exception cref="System.Exception"></exception>
        public T this[int index]
        {
            get
            {
                using (ObjectPoolItem<Stream> c = _readerPool.GetObject())
                {
                    var offset = dataLookup[index];

                    var o = SequentialDataCacheStructures.Page.ReadSingleDataAt<T>(c.Instance, offset);

                    return o;
                }

                /*
                int pageIndex = index / _head.PageSizeLimit;
                if (pageIndex > _head.LastPage.PageIndex)
                    throw new ArgumentOutOfRangeException("index");

                int pageDataIndex = index % _head.PageSizeLimit;
                
                using (ObjectPoolItem<Stream> c = _readerPool.GetObject())
                {

                    SequentialDataCacheStructures.Page page = _head.OpenFirstPage(c.Instance);
                    while (page.PageIndex < pageIndex)
                    {
                        page = page.ReadNextPage(c.Instance);
                    }

                    var o = page.ReadSingleDataAt<T>(c.Instance, pageDataIndex);

                    return o;
                }*/
            }
        }

        #region IDisposable Members

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion

        #region ISequentialDataCache<T> Members

        /// <summary>
        /// Gets the count of all items.
        /// </summary>
        /// <value>
        /// The count of all items.
        /// </value>
        public int Count
        {
            get { return _head.Count; }
        }

        /// <summary>
        /// Gets the path.
        /// </summary>
        /// <value>
        /// The path.
        /// </value>
        public string Path
        {
            get { return _path; }
        }

        public IEnumerator<T> GetEnumerator()
        {
            return new Enumerator(this);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        /// <summary>
        /// Adds the specified item.
        /// </summary>
        /// <param name="item">The item.</param>
        public void Add(T item)
        {
            lock (_writerLock)
            {
                _head.Add(_writerStream, item, this);

            }
        }

        object _writerLock = new object();

        /// <summary>
        /// Clears this instance.
        /// </summary>
        public void Clear()
        {
            lock (_writerLock)
            {
                _head.Clear(_writerStream);
                pageLookup.Clear();
                dataLookup.Clear();
            }
        }


        /// <summary>
        /// Closes this instance.
        /// </summary>
        public void Close()
        {
            if (closed)
                return;

            _readerPool.Dispose();
            _readerPool = null;

            _writerStream.Flush();
            _writerStream.Close();
            _writerStream.Dispose();
            _writerStream = null;

            if (_deleteOnClose)
                Delete();

            pageLookup.Clear();
            dataLookup.Clear();

            closed = true;
        }

        /// <summary>
        /// Deletes this instance.
        /// </summary>
        public void Delete()
        {
            if (File.Exists(_path))
                File.Delete(_path);
            }

        #endregion

        /// <summary>
        /// Initializes the SequentialDataCache{T} in Temp folder with random name.
        /// </summary>
        /// <param name="pageSizeLimit">The page size limit.</param>
        /// <param name="deleteOnClose">if set to <c>true</c> [delete on close].</param>
        /// <returns></returns>
        public static SequentialDataCache<T> Initialize(int pageSizeLimit = 100, bool deleteOnClose = false)
        {
            var tempPath = System.IO.Path.GetTempFileName();
            return Initialize(tempPath, pageSizeLimit, deleteOnClose);
        }

        /// <summary>
        /// Initializes the SequentialDataCache{T}.
        /// </summary>
        /// <param name="path">The path.</param>
        /// <param name="pageSizeLimit">The page size limit.</param>
        /// <param name="deleteOnClose">if set to <c>true</c> [delete on close].</param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentNullException">path</exception>
        /// <exception cref="System.ArgumentOutOfRangeException">pageSizeLimit;pageSizeLimit must be greater than 0</exception>
        public static SequentialDataCache<T> Initialize(string path, int pageSizeLimit = 100, bool deleteOnClose = false)
        {
            if (path == null) throw new ArgumentNullException("path");

            if (pageSizeLimit < 1)
                throw new ArgumentOutOfRangeException("pageSizeLimit", pageSizeLimit,
                                                      "pageSizeLimit must be greater than 0");

            //create header
            using (FileStream fileStream = File.Open(path,
                                                     FileMode.OpenOrCreate, FileAccess.ReadWrite,
                                                     FileShare.Read))
            {
                var head = new SequentialDataCacheStructures.Head
                               {
                                   PageSizeLimit = pageSizeLimit,
                               };
                head.Initialize();

                head.Persist(fileStream);

                fileStream.Flush();
                fileStream.Close();
            }

            //initialize new LinqDataCache
            var c = new SequentialDataCache<T>(path, deleteOnClose);

            //return collection
            return c;
        }

        private void Dispose(bool disposing)
        {
            if (disposed)
                return;

            if (disposing)
                Close();

            disposed = true;
        }

        ~SequentialDataCache()
        {
            Dispose(false);
        }

        #region Nested type: Enumerator

        /// <summary>
        /// SequentialDataCache enumerator
        /// </summary>
        private class Enumerator : IEnumerator<T>
        {
            private readonly SequentialDataCache<T> _coll;
            private readonly ObjectPoolItem<Stream> _reader;
            private int _currentIndex;
            private T[] _currentPageData;
            private SequentialDataCacheStructures.Page _page;

            /// <summary>
            /// Initializes a new instance of the <see cref="Enumerator" /> class.
            /// </summary>
            /// <param name="coll">The coll.</param>
            public Enumerator(SequentialDataCache<T> coll)
            {
                _coll = coll;

                //get reader
                _reader = coll._readerPool.GetObject();

                //reset 
                Reset();
            }

            #region IEnumerator<T> Members

            /// <summary>
            /// Gets the current.
            /// </summary>
            /// <value>
            /// The current.
            /// </value>
            public T Current
            {
                get { return _currentPageData[_currentIndex]; }
            }

            /// <summary>
            /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
            /// </summary>
            public void Dispose()
            {
                _reader.Dispose();
            }

            /// <summary>
            /// Gets the current.
            /// </summary>
            /// <value>
            /// The current.
            /// </value>
            object IEnumerator.Current
            {
                get { return Current; }
            }

            /// <summary>
            /// Advances the enumerator to the next element of the collection.
            /// </summary>
            /// <returns>
            /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
            /// </returns>
            public bool MoveNext()
            {
                _currentIndex++;

                if (_currentIndex > _page.DataCount - 1)
                {
                    if (_page.NextPageOffset != -1)
                    {
                        _reader.Instance.Seek(_page.NextPageOffset, SeekOrigin.Begin);
                        _page = _reader.Instance.ReadStruct<SequentialDataCacheStructures.Page>();
                        _currentPageData = _page.ReadAllData<T>(_reader.Instance);
                        _currentIndex = 0;

                        //if have no data in the page break enumeration
                        if (_page.DataCount == 0)
                            return false;
                    }
                    else
                        return false;
                }

                return true;
            }

            /// <summary>
            /// Sets the enumerator to its initial position, which is before the first element in the collection.
            /// </summary>
            public void Reset()
            {
                _page = _coll._head.OpenFirstPage(_reader.Instance);
                _currentPageData = _page.ReadAllData<T>(_reader);
                _currentIndex = -1;
            }

            #endregion
        }

        #endregion
    }
}